home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1998 September
/
Macworld (1998-09).dmg
/
Shareware World
/
Info
/
For Developers
/
MacZoop 1.8.3
/
Required Classes
/
Z Sources
/
ZMenuBar.cpp
< prev
next >
Wrap
Text File
|
1998-07-09
|
37KB
|
1,446 lines
/*************************************************************************************************
*
*
* MacZoop - "the framework for the rest of us"
*
*
*
* ZMenuBar.cpp -- the menubar manager object
*
*
*
*
*
* © 1996, Graham Cox
*
*
*
*
*************************************************************************************************/
#include "MacZoop.h"
#include "ProjectSettings.h"
#include "ZCommander.h"
#ifndef __BALLOONS__
#include <balloons.h>
#endif
extern ZCommander* gCurHandler;
short gFontMenuID = 0;
/*--------------------------------*** DESTRUCTOR ***---------------------------------*/
ZMenuBar::~ZMenuBar()
{
// menubar must be visible when we quit
if ( mbHiding == MBAR_HIDE )
ShowHideMenuBar( MBAR_SHOW );
if ( theMenuCmds )
ForgetObject( theMenuCmds );
if ( theMenus )
ForgetObject( theMenus );
ReleaseResource((Handle) mBarH );
}
/*--------------------------------*** INITMENUBAR ***--------------------------------*/
/*
initialise the menubar from a MBAR resource
---------------------------------------------------------------------------------------*/
void ZMenuBar::InitMenuBar()
{
// create an array for holding our command info
FailNIL( theMenuCmds = new ZArray( sizeof( MenuCmd )));
FailNIL( theMenus = new ZArray( sizeof( MenuInfRec )));
mbCount = 0;
miSeed = 1;
wmMenuID = 0;
rbPending = FALSE;
inDispatch = FALSE;
// initially menubar is visible
mBarHeight = GetMBarHeight();
mbHiding = MBAR_SHOW;
// load the MBAR resource
mBarH = (short**) GetResource( 'MBAR', mBarID );
FailOSErr( ResError());
HNoPurge((Handle) mBarH );
// read in menus from the MBAR resource
LoadMenus();
HPurge((Handle) mBarH );
// add the standard items (DA's) to the Apple menu
AppendStdItems( kAppleMenuID );
menuCheckChar = checkMark;
// note how many items are in the help menu so we can correctly
// identify any items we have added
MenuHandle helpMenuH;
FailOSErr( HMGetHelpMenuHandle( &helpMenuH ));
mHelpOffset = CountMenuItems( helpMenuH );
}
/*-------------------------------*** CLICKMENUBAR ***--------------------------------*/
/*
handle mouse click in the menubar
---------------------------------------------------------------------------------------*/
void ZMenuBar::ClickMenuBar( const Point mousePt )
{
long mSelect;
// initially disable all menu items
DimMenus();
// ask the command chain to reenable the relevant menu items
if ( gCurHandler )
gCurHandler->UpdateMenus();
// now track and select the menus
mSelect = TrackMenuBar( mousePt );
// dispatch the command
DispatchCommand( mSelect );
}
/*-------------------------------*** TRACKMENUBAR ***--------------------------------*/
/*
track the menubar, returning the item chosen.
---------------------------------------------------------------------------------------*/
long ZMenuBar::TrackMenuBar( const Point mouse )
{
PauseCursorAnimation( 0 );
return MenuSelect( mouse );
}
/*------------------------------*** UPDATEMENUBAR ***--------------------------------*/
/*
redraw the menubar. If the menubar is currently dispatching to the command chain, set
a flag in order to cause us to get redrawn when the command completes. If you don't do
this, the menubar can get itself into a bad visual state.
---------------------------------------------------------------------------------------*/
void ZMenuBar::UpdateMenuBar()
{
if ( inDispatch || rbPending )
rbPending = TRUE;
else
DrawMenuBar();
}
/*------------------------------*** DISPATCHCOMMAND ***------------------------------*/
/*
look up the command and send it up the chain
---------------------------------------------------------------------------------------*/
void ZMenuBar::DispatchCommand( const long mSelect )
{
MenuCmd mCmd;
GrafPtr savePort;
GetPort( &savePort );
// if an item was chosen, look up the command and send it up the chain
mCmd.theCmd = noCommand;
if ( HiWord( mSelect) != 0 && gCurHandler )
{
// set the zoom source rect in case the command spawns a window
#if _ZOOM_RECT_FX
Rect r;
GetMenuTitleRect( HiWord( mSelect ), &r );
InsetRect( &r, 20, 4 );
SetGlobalZoomSource( &r );
#endif
// if the nominated windows menu, handle the window selection
if ( HiWord( mSelect ) == wmMenuID )
gWindowManager->SelectWindowFromMenu( LoWord( mSelect ));
FindMCmd( mSelect, &mCmd );
if ( mCmd.theCmd != parentCmd )
{
// if the command was found, pass it up the chain. If not found, we still pass it
// up the chain, but this time as the direct menuID and itemID chosen. Commander
// classes can choose which of the two methods (or perhaps both?) to adopt.
inDispatch = TRUE;
if ( mCmd.theCmd != noCommand )
gCurHandler->HandleCommand( mCmd.theCmd );
else
gCurHandler->HandleCommand( HiWord( mSelect ), LoWord( mSelect ));
inDispatch = FALSE;
}
SetTitleHilite( 0, FALSE );
if ( rbPending )
{
rbPending = FALSE;
UpdateMenuBar();
}
SetPort( savePort );
}
}
/*---------------------------------*** ENABLECMD ***---------------------------------*/
/*
enable the menu item associated with the command
---------------------------------------------------------------------------------------*/
void ZMenuBar::EnableCommand( const long cmd )
{
short m = 0, i = 0;
FindCommand( cmd, &m, &i );
if ( m && i )
EnableCommand( m, i );
}
/*---------------------------------*** ENABLECMD ***---------------------------------*/
void ZMenuBar::EnableCommand( const short menuID, const short itemID )
{
MenuHandle mH = FindMenuID( menuID );
if ( mH )
{
EnableItem( mH, itemID );
if ( itemID == 0 )
UpdateMenuBar();
}
}
/*---------------------------------*** DISABLECMD ***--------------------------------*/
/*
disable the menu item associated with the command
---------------------------------------------------------------------------------------*/
void ZMenuBar::DisableCommand( const long cmd )
{
short m = 0, i = 0;
FindCommand( cmd, &m, &i );
if ( m && i )
DisableCommand( m, i );
}
/*---------------------------------*** DISABLECMD ***--------------------------------*/
void ZMenuBar::DisableCommand( const short menuID, const short itemID )
{
MenuHandle mH = FindMenuID( menuID );
if ( mH )
{
DisableItem( mH, itemID );
if ( itemID == 0 )
UpdateMenuBar();
}
}
/*--------------------------------*** CHECKCOMMAND ***-------------------------------*/
/*
check the menu command on or off.
---------------------------------------------------------------------------------------*/
void ZMenuBar::CheckCommand( const long cmd, const Boolean checkOnOff )
{
short m = 0, i = 0;
FindCommand( cmd, &m, &i );
if ( m && i )
CheckCommand( m, i, checkOnOff );
}
/*--------------------------------*** CHECKCOMMAND ***-------------------------------*/
void ZMenuBar::CheckCommand( const short menuID, const short itemID, const Boolean checkOnOff )
{
MenuHandle mH = FindMenuID( menuID );
if ( mH )
SetItemMark( mH, itemID, checkOnOff? menuCheckChar : noMark );
}
/*--------------------------------*** CHECKCOMMAND ***-------------------------------*/
void ZMenuBar::CheckCommand( const short menuID, Str255 itemString, const Boolean checkOnOff )
{
// check the item with the text matching that passed (not case sensitive)
MenuHandle mH = FindMenuID( menuID );
Str255 iMatch;
if ( mH )
{
short m = CountMenuItems( mH );
do
{
GetMenuItemText( mH, m, iMatch );
if ( EqualString( itemString, iMatch, FALSE, TRUE ))
{
SetItemMark( mH, m, checkOnOff? menuCheckChar : noMark );
break;
}
}
while( --m );
}
}
/*----------------------------*** CHECKCOMMANDWITHCHAR ***---------------------------*/
void ZMenuBar::CheckCommandWithChar( const long cmd, const char checkChar )
{
short m = 0, i = 0;
FindCommand( cmd, &m, &i );
if ( m && i )
{
MenuHandle mH = FindMenuID( m );
if ( mH )
SetItemMark( mH, i, checkChar );
}
}
/*----------------------------*** CHECKCOMMANDWITHCHAR ***---------------------------*/
void ZMenuBar::CheckCommandWithChar( const short menuID, Str255 itemString, const char checkChar )
{
MenuHandle mH = FindMenuID( menuID );
Str255 iMatch;
if ( mH )
{
short m = CountMenuItems( mH );
do
{
GetMenuItemText( mH, m, iMatch );
if ( EqualString( itemString, iMatch, FALSE, TRUE ))
{
SetItemMark( mH, m, checkChar );
break;
}
}
while( --m );
}
}
/*-------------------------------*** SETCOMMANDTEXT ***------------------------------*/
/*
set the text of a menu item to the desired string. Can be accessed by command or item.
---------------------------------------------------------------------------------------*/
void ZMenuBar::SetCommandText( const long cmd, Str255 aText )
{
short m = 0, i = 0;
FindCommand( cmd, &m, &i );
if ( m && i )
SetCommandText( m, i, aText );
}
/*-------------------------------*** SETCOMMANDTEXT ***------------------------------*/
void ZMenuBar::SetCommandText( const short menuID, const short itemID, Str255 aText )
{
MenuHandle mH;
mH = FindMenuID( menuID );
if ( mH )
SetMenuItemText( mH, itemID, aText );
}
/*-------------------------------*** SETCOMMANDTEXT ***------------------------------*/
void ZMenuBar::SetCommandText( const long cmd, const short strListID, const short strIndex )
{
Str255 aText;
GetIndString( aText, strListID, strIndex );
if ( aText[0] > 0 )
SetCommandText( cmd, aText );
}
/*-------------------------------*** SETCOMMANDTEXT ***------------------------------*/
void ZMenuBar::SetCommandText( const short menuID, const short itemID, const short strListID, const short strIndex )
{
Str255 aText;
GetIndString( aText, strListID, strIndex );
if ( aText[0] > 0 )
SetCommandText( menuID, itemID, aText );
}
/*----------------------------*** SETCOMMANDTEXTSTYLE ***---------------------------*/
void ZMenuBar::SetCommandTextStyle( const long cmd, Style aStyle )
{
short m = 0, i = 0;
MenuHandle mh;
FindCommand( cmd, &m, &i );
if ( m && i )
{
mh = FindMenuID( m );
if ( mh )
SetItemStyle( mh, i, aStyle );
}
}
/*-------------------------------*** SHOWHIDEMENUBAR ***-----------------------------*/
/*
Show or hide the menubar. This can be used in two ways. You can set the menubar hidden
completely by passing MBAR_HIDE, and reshow it with MBAR_SHOW. OR you can pass in
MBAR_HIDE_MOUSEAWARE and a global mouse location to show or hide the bar dynamically as
the user moves the mouse. ZEventHandler will provide this functionality if you define
_AUTO_MBAR_HIDING.
---------------------------------------------------------------------------------------*/
void ZMenuBar::ShowHideMenuBar( MBarHiding mHiding, Point gMouseLoc )
{
Boolean showIt;
Rect msRect;
GDHandle theDevice;
RgnHandle temp;
if ( mHiding != mbHiding )
{
// state has changed, or we want to determine it from mouse position
theDevice = GetMainDevice();
msRect = (*theDevice)->gdRect;
msRect.bottom = msRect.top + mBarHeight;
if ( mHiding == MBAR_HIDE_MOUSEAWARE )
{
showIt = PtInRect( gMouseLoc, &msRect );
// if menubar already in indicated state, do nothing
if (( showIt && ( mbHiding == MBAR_SHOW )) ||
( !showIt && ( mbHiding == MBAR_HIDE )))
return;
}
else
showIt = ( mHiding == MBAR_SHOW );
FailNIL( temp = NewRgn());
RectRgn( temp, &msRect );
if ( showIt )
{
LMSetMBarHeight( mBarHeight );
DiffRgn(LMGetGrayRgn(), temp, LMGetGrayRgn());
UpdateMenuBar();
mbHiding = MBAR_SHOW;
}
else
{
LMSetMBarHeight( 0 );
UnionRgn(LMGetGrayRgn(), temp, LMGetGrayRgn());
mbHiding = MBAR_HIDE;
}
// calculate and refresh vis regions of windows
PaintBehind( FrontWindow(), temp );
CalcVisBehind( FrontWindow(), temp );
DisposeRgn( temp );
}
}
/*-------------------------------*** SHOWHIDEMENUBAR ***-----------------------------*/
void ZMenuBar::ShowHideMenuBar( MBarHiding mHiding )
{
Point unused = { 0, 0 };
ShowHideMenuBar( mHiding, unused );
}
/*-----------------------------*** NOMINATEWINDOWSMENU ***---------------------------*/
/*
This method can be used to set up an automatic "Windows" menu. To do this, call this
method once your bar is built. This will nominate the menu with the ID passed as the
Windows menu. Any items in the menu already will be retained and work as normal. As
windows are added and deleted in the window manager, this will maintain the menu for you.
---------------------------------------------------------------------------------------*/
void ZMenuBar::NominateWindowsMenu( const short menuID )
{
if ( wmMenuID == 0 )
{
MenuHandle mH = FindMenuID( menuID );
if ( mH )
{
wmMenuID = menuID;
SetMenuDimming( menuID, disableCmdsOnly );
// we need to work hand-in-hand with the window manager to
// keep track of the windows. The Window Manager is best placed to
// do all this, so we simply hand off the menu to it.
gWindowManager->SetWindowsMenu( mH );
}
}
}
/*-------------------------------*** INSERTHELPITEM ***------------------------------*/
/*
append the text to the help menu, and return the item number of the resulting item. To
process this item, override HandleCommand and look for kHMHelpMenuID and the item returned
from this method.
---------------------------------------------------------------------------------------*/
short ZMenuBar::AppendHelpItem( Str255 itemText )
{
short i;
MenuHandle helpMenuH;
FailOSErr( HMGetHelpMenuHandle( &helpMenuH ));
i = mHelpOffset + 1;
AppendMenu( helpMenuH, itemText );
return i;
}
/*------------------------------*** APPENDMENUTOBAR ***------------------------------*/
/*
This can be called to add menus "on the fly" into the bar. This reads a menu from a
MENU or CMNU resource, parses its comands and inserts the menu. Menus can only be added
to the end of the bar. You need to call UpdateMenuBar after this or a series of these.
---------------------------------------------------------------------------------------*/
void ZMenuBar::AppendMenuToBar( const short menuID )
{
Handle temp;
temp = GetResource( 'CMNU', menuID );
if ( temp )
{
LoadCMNUMenu( menuID );
ReleaseResource( temp );
}
else
LoadMenu( menuID );
mbCount++;
}
/*-----------------------------*** REMOVEMENUFROMBAR ***-----------------------------*/
/*
This can be called to remove a menu added with the above routine. You need to call
UpdateMenuBar after this or a series of these.
---------------------------------------------------------------------------------------*/
void ZMenuBar::RemoveMenuFromBar( const short menuID )
{
MenuHandle mH;
FailNILParam( mH = GetMenuHandle( menuID ));
// remove the command entries for this menu and any submenus it is the parent of.
UnloadMenu( mH );
mbCount--;
}
/*------------------------------*** UPDATESTYLEMENU ***------------------------------*/
/*
sets checkmarks agains standard syle command according to style info passed
---------------------------------------------------------------------------------------*/
void ZMenuBar::UpdateStyleMenu( TEStyleRunInfo* runInfo )
{
EnableCommand( kCmdPlainText );
EnableCommand( kCmdBoldText );
EnableCommand( kCmdItalicText );
EnableCommand( kCmdUnderlineText );
EnableCommand( kCmdOutlineText );
EnableCommand( kCmdShadowText );
EnableCommand( kCmdCondensedText );
EnableCommand( kCmdExtendedText );
Style curStyle = runInfo->runStyles;
Style cs = runInfo->contStyles;
Boolean continuousRun = ( cs == 0xFF );
if (( curStyle & 0x7F ) == 0 )
CheckCommand( kCmdPlainText, TRUE );
else
{
if ( curStyle & kPlainStyle )
CheckCommandWithChar( kCmdPlainText, '-' );
if ( curStyle & bold )
CheckCommandWithChar( kCmdBoldText, ( cs & bold ) == bold? checkMark : '-' );
if ( curStyle & italic )
CheckCommandWithChar( kCmdItalicText, ( cs & italic ) == italic? checkMark : '-' );
if ( curStyle & underline )
CheckCommandWithChar( kCmdUnderlineText, ( cs & underline ) == underline? checkMark : '-' );
if ( curStyle & outline )
CheckCommandWithChar( kCmdOutlineText, ( cs & outline ) == outline? checkMark : '-' );
if ( curStyle & shadow )
CheckCommandWithChar( kCmdShadowText, ( cs & shadow ) == shadow? checkMark : '-' );
if ( curStyle & condense )
CheckCommandWithChar( kCmdCondensedText, ( cs & condense ) == condense? checkMark : '-' );
if ( curStyle & extend )
CheckCommandWithChar( kCmdExtendedText, ( cs & extend ) == extend? checkMark : '-' );
}
}
/*----------------------------*** UPDATEFONTSIZEMENU ***-----------------------------*/
/*
sets checkmark against current font size, plus sets outline style for real font sizes
in the current font. This iterates through the possible commands so it will work no
matter where you have decided to place your size commands. Neat.
---------------------------------------------------------------------------------------*/
void ZMenuBar::UpdateFontSizeMenu( TEStyleRunInfo* runInfo )
{
Boolean contFont, contSize;
contFont = runInfo->fr & kFontIsContinuous;
contSize = runInfo->fr & kSizeIsContinuous;
long fCmd;
short i;
Str255 fontName;
for ( fCmd = kCmdStdFontSize7; fCmd <= kCmdStdFontSize72; fCmd++ )
{
EnableCommand( fCmd );
if ( contFont && RealFont( runInfo->fonts[0], fCmd - kStdFontSizeBase ))
SetCommandTextStyle( fCmd, outline );
else
SetCommandTextStyle( fCmd, 0 );
}
if ( contSize )
CheckCommand( kStdFontSizeBase + runInfo->sizes[0], TRUE );
else
{
// non-continuous sizes, so mark all the listed sizes
for ( i = 0; i < runInfo->numSizes; i++ )
CheckCommandWithChar( runInfo->sizes[i] + kStdFontSizeBase, '-' );
}
if ( gFontMenuID )
{
if ( contFont )
{
GetFontName( runInfo->fonts[0], fontName );
CheckCommand( gFontMenuID, fontName, TRUE );
}
else
{
// non-continous fonts, so mark all the listed fonts
for ( i = 0; i < runInfo->numFonts; i++ )
{
GetFontName( runInfo->fonts[i], fontName );
CheckCommandWithChar( gFontMenuID, fontName, '-' );
}
}
}
}
/*---------------------------------*** LOADMENUS ***---------------------------------*/
/*
for each menu in the MBAR, call LoadMenu. This will deal with hierarchical menus, etc.
This method assumes mBarH is valid, and won't be purged.
---------------------------------------------------------------------------------------*/
void ZMenuBar::LoadMenus( const Boolean autoInstall )
{
short i, menuID;
Handle temp;
// how many menus in MBAR resource? This is first item in resource.
mbCount = (*mBarH)[0];
// iterate through, looking for MENU or CMNU resources
for ( i = 1; i <= mbCount; i++ )
{
menuID = (*mBarH)[i];
// if CMNU resource is available, use that. Otherwise, use MENU resource,
// possibly parsing it for command numbers
temp = GetResource( 'CMNU', menuID );
if ( temp )
{
LoadCMNUMenu( menuID, FALSE, autoInstall );
ReleaseResource( temp );
}
else
LoadMenu( menuID, FALSE, autoInstall );
}
}
/*----------------------------------*** DIMMENUS ***---------------------------------*/
/*
dim all of the menu items, according to their flags.
---------------------------------------------------------------------------------------*/
void ZMenuBar::DimMenus()
{
// This originally operated by walking the low-memory global menuList, but that does
// not work when 3rd party extensions install extra menus. Thus we now use our private
// list of menus to do this operation so we only affect our own menus.
short i;
MenuInfRec mRec;
for ( i = 1; i <= theMenus->CountItems(); i++ )
{
// find this menu in our list, and dim it according to the
// flags there. (Thanks to Jean-Yves Pochez for the improvements to this method)
mRec.macMenu = NULL;
theMenus->GetArrayItem( &mRec, i );
if ( mRec.macMenu )
{
// menu is in our list, so dim it. Note that all menus, including all of
// the attached hierarchical menus are dimmed by iterating this list. Thus
// PredimMenu does not need to be recursive, and isn't.
PredimMenu( mRec.macMenu );
}
}
// if auto Windows menu in use, set that up as well
if ( wmMenuID > 0 )
gWindowManager->BuildWindowsMenu();
}
/*-------------------------------*** SETMENUDIMMING ***------------------------------*/
/*
set the dimming options for a particular menu. By default, all menus are dimmed auto-
matically, but if you don't want them dimmed, or partially dimmed, call this with the
required options.
---------------------------------------------------------------------------------------*/
void ZMenuBar::SetMenuDimming( const short menuID, const DimmingOptions dimOpts )
{
MenuInfRec mr;
Boolean bUpdate;
mr.macMenu = NULL;
FindMenuInfo( menuID, &mr );
if ( mr.macMenu )
{
// if this call will change the state of the dimTitle flag, we need to cause an
// update to the menubar so that the user sees the change
bUpdate = (( mr.mDimming ^ dimOpts ) & dimTitle ) != 0;
mr.mDimming = dimOpts;
theMenus->SetArrayItem( &mr, mr.mIndex );
// update the bar if necessary
if ( bUpdate )
{
// enable or disable title item
if ( dimOpts & dimTitle )
(*mr.macMenu)->enableFlags &= 0xFFFFFFFE;
else
(*mr.macMenu)->enableFlags |= 1;
UpdateMenuBar();
}
}
}
/*----------------------------------*** LOADMENU ***---------------------------------*/
/*
can be called recursively- try to load the menu with the ID and any submenus it refers
to. If MENU resources are not found, try CMNU resources.
---------------------------------------------------------------------------------------*/
void ZMenuBar::LoadMenu( const short menuID, Boolean isHMenu, Boolean autoInstall )
{
short i, mCount, cmdChar, subID;
MenuHandle mH;
MenuCmd mCmd;
MenuInfRec mRec;
Str255 iText;
FailNILRes( mH = GetMenu( menuID ));
// set up info record for this menu
mRec.menuID = menuID;
mRec.mIndex = miSeed++;
mRec.macMenu = mH;
mRec.mDimming = dimCommands;
mRec.mIsResource = TRUE;
theMenus->AppendItem( &mRec );
// look through the menu for submenus, and recursively add them
mCount = CountMenuItems( mH );
for( i = 1; i <= mCount; i++ )
{
GetMenuItemText( mH, i, iText );
if ( iText[1] != '-' )
{
// make a command entry for the item
mCmd.menuID = menuID;
mCmd.itemID = i;
mCmd.macMenu = mH;
ParseMenuItem( iText, &mCmd.theCmd );
// menus created using GetMenu are resources, CMNU are not. This is flagged so
// we can dispose of it correctly.
mCmd.cmdFlags = autoUnCheck | ( isHMenu? 0 : isPrimaryMenu ) | menuIsResource;
mCmd.subMenuID = 0;
SetMenuItemText( mH, i, iText );
GetItemCmd( mH, i, &cmdChar );
if ( cmdChar == hMenuCmd )
{
// has a submenu, so load it:
GetItemMark( mH, i, &subID );
// need to determine if submenu is a MENU or CMNU resource:
Handle cmnuH;
cmnuH = GetResource( 'CMNU', subID );
if ( cmnuH )
{
LoadCMNUMenu( subID, TRUE, autoInstall );
ReleaseResource( cmnuH );
}
else
LoadMenu( subID, TRUE, autoInstall );
mCmd.subMenuID = subID;
mCmd.theCmd = parentCmd;
}
theMenuCmds->AppendItem( &mCmd );
}
}
// add the menu to the system list
if ( autoInstall )
InsertMenu( mH, isHMenu? hierMenu : 0 );
}
/*--------------------------------*** LOADCMNUMENU ***-------------------------------*/
/*
ditto for CMNU resources- this can parse that format of resource.
---------------------------------------------------------------------------------------*/
void ZMenuBar::LoadCMNUMenu( const short menuID, Boolean isHMenu, Boolean autoInstall )
{
CMNUResHdl cH;
MenuCmd mCmd;
MenuInfRec mRec;
MenuHandle mH;
Ptr cmP, cmItemText;
short itemID;
cH = ( CMNUResHdl ) GetResource( 'CMNU', menuID );
if ( cH )
{
// we need to do two things with this menu- a) make a normal menu handle that
// can be inserted into the menu manager list, and b) a set of menuCmd records
// so that we can look up the command when an item is chosen.
FailNIL( mH = NewMenu( menuID, (ConstStr255Param) &(*cH)->mTitle ));
// set up info record for this menu
mRec.menuID = menuID;
mRec.mIndex = miSeed++;
mRec.macMenu = mH;
mRec.mDimming = dimCommands;
mRec.mIsResource = FALSE;
theMenus->AppendItem( &mRec );
HLock((Handle) cH );
// we need to iterate through the items in the CMNU and build our two structures
// as needed. To do this efficiently, we lock the handle and keep a running pointer.
cmItemText = cmP = &(*cH)->mTitle + (*cH)->mTitle + 1;
itemID = 0;
// we're now at the start of the first item's text. We now scan through each item
// building both the real menu and the command structure
while( *cmItemText != 0 )
{
itemID++;
cmP += *cmItemText + 1; // point to top of "interesting" info
// add text of item, making sure meta-characters are ignored. Note that
// a dividing line still works correctly
AppendMenu( mH, "\px" );
SetMenuItemText( mH, itemID, (ConstStr255Param) cmItemText );
// add command entry if not a dividing line
if ( cmItemText[1] != '-' )
{
// make menu item match info
SetItemIcon ( mH, itemID, ((CMNUEntryPtr) cmP)->iconID );
SetItemCmd ( mH, itemID, ((CMNUEntryPtr) cmP)->keyEqu );
SetItemMark ( mH, itemID, ((CMNUEntryPtr) cmP)->markChar );
SetItemStyle( mH, itemID, ((CMNUEntryPtr) cmP)->iStyle );
mCmd.menuID = menuID;
mCmd.itemID = itemID;
mCmd.subMenuID = 0;
mCmd.macMenu = mH;
mCmd.cmdFlags = autoUnCheck | ( isHMenu? 0 : isPrimaryMenu );
mCmd.theCmd = noCommand;
// if there's a sub-menu, we'll need to find and load it too. This involves
// a recursion to this function or to LoadMenu.
if (((CMNUEntryPtr) cmP)->keyEqu == hMenuCmd )
{
mCmd.subMenuID = ((CMNUEntryPtr) cmP)->markChar;
// is this a CMNU or a MENU resource?
Handle cmnuH = GetResource( 'CMNU', mCmd.subMenuID );
if ( cmnuH )
{
LoadCMNUMenu( mCmd.subMenuID, TRUE, autoInstall );
ReleaseResource( cmnuH );
}
else
LoadMenu( mCmd.subMenuID, TRUE, autoInstall );
mCmd.theCmd = parentCmd;
}
else
{
// set up the command entry for the item. The command number
// may follow a pad byte if it would otherwise be at an odd address, so the
// data lies 4 or 5 bytes away from where cmP is now:
if ((unsigned long) cmP & 1 )
mCmd.theCmd = *(long*)( cmP + sizeof( CMNUEntry ) + 1 );
else
mCmd.theCmd = *(long*)( cmP + sizeof( CMNUEntry ));
}
// add the item to our command array
theMenuCmds->AppendItem( &mCmd );
}
// set the pointers to the next item. This is 8 or 9 bytes away depending on
// whether the resulting address is odd or even
cmP += 8;
if ((unsigned long) cmP & 1 )
cmP++;
cmItemText = cmP;
}
HUnlock((Handle) cH );
// insert the menu we just built into the system list
if ( autoInstall )
InsertMenu( mH, isHMenu? hierMenu : 0 );
}
}
/*---------------------------------*** UNLOADMENU ***--------------------------------*/
/*
removes this menu from the command list and un-inserts itself. It also calls itself to
deal with its submenus. Called by RemoveMenuFromBar. Use with care.
---------------------------------------------------------------------------------------*/
void ZMenuBar::UnloadMenu( MenuHandle mH )
{
long m;
MenuCmd mCmd;
Boolean resStatus = FALSE;
Boolean isResMenu = FALSE;
for( m = theMenuCmds->CountItems(); m > 0; m-- )
{
theMenuCmds->GetArrayItem( &mCmd, m );
// if this is one pertaining to the menu, delete it from the array. Also
// if its a parent item, call this again to delete the submenu too.
if ( mH == mCmd.macMenu )
{
theMenuCmds->DeleteItem( m );
// is this a parent?
if ( mCmd.theCmd == parentCmd &&
mCmd.subMenuID != 0 )
{
// yes, so recurse and delete that too
MenuHandle smH = GetMenuHandle( mCmd.subMenuID );
if ( smH )
UnloadMenu( smH );
}
if ( !resStatus )
{
isResMenu = ( mCmd.cmdFlags & menuIsResource ) == menuIsResource;
resStatus = TRUE;
}
}
}
// remove the menu from the system menu list
DeleteMenu((*mH)->menuID );
// dispose or release the menu handle according to whether
// it was a resource or not
if ( isResMenu )
ReleaseResource((Handle) mH );
else
DisposeMenu( mH );
}
/*---------------------------------*** PREDIMMENU ***--------------------------------*/
/*
can be called recursively- dim this menu and any submenus it owns.
---------------------------------------------------------------------------------------*/
void ZMenuBar::PredimMenu( MenuHandle theMenu )
{
short m, i, cmd;
MenuInfRec mi;
if ( theMenu )
{
// initially set all items to be dimmed except the title ( we make one
// exception- the apple menu only has the first item dimmed );
if ((*theMenu)->menuID == kAppleMenuID )
(*theMenu)->enableFlags = 0xFFFFFFF9;
else
{
// get the info rec for this menu
FindMenuInfo((*theMenu)->menuID, &mi );
// dim items according to dimming flags except title
// if title already dimmed, it is not re-enabled here.
if (( mi.mDimming & 0x0F ) != neverDim )
(*theMenu)->enableFlags &= 0x00000001;
// look through the items for submenus
m = CountMenuItems( theMenu );
for( i = 1; i <= m; i++ )
{
GetItemCmd( theMenu, i, &cmd );
if ( cmd == hMenuCmd )
{
// Enable the parent item if permitted
EnableItem( theMenu, i );
}
else
{
// if the item has no command and we don't want these dimming, enable it
if ( mi.mDimming & dimCommands )
{
MenuCmd mc;
mc.theCmd = 0;
FindMCmd((((long)(*theMenu)->menuID ) << 16 ) | i, &mc );
if ( mc.theCmd == 0 )
EnableItem( theMenu, i );
}
// in case the item is checked, uncheck it
SetItemMark( theMenu, i, noMark );
}
}
}
}
}
/*-------------------------------*** PARSEMENUITEM ***-------------------------------*/
/*
extract command info from the item for TCL-style menu items
---------------------------------------------------------------------------------------*/
void ZMenuBar::ParseMenuItem( Str255 iText, long* aCmd )
{
*aCmd = noCommand;
// a valid command is associated with the item by appending a hash sysmbol (#),
// followed by the command number as a string. Here we extract the number and
// modify the string to exclude it.
unsigned char i = 1;
Str15 subStr;
// search for # char
while((iText[i] != '#') && (i < iText[0]))
i++;
if ( i < iText[0] )
{
// extract substring which is command number in string form
BlockMoveData( &iText[i + 1], &subStr[1], iText[0] - i );
subStr[0] = iText[0] - i;
StringToNum( subStr, aCmd );
// truncate string
iText[0] = i - 1;
}
}
/*------------------------------*** APPENDSTDITEMS ***-------------------------------*/
/*
appends DA names or FONT names to the menu
---------------------------------------------------------------------------------------*/
void ZMenuBar::AppendStdItems( const short menuID, const short iType )
{
MenuHandle mH;
mH = FindMenuID( menuID );
if ( mH )
AppendResMenu( mH, ( iType == appendDANames )? 'DRVR' : 'FONT' );
// if we are creating a font menu, record the menuID in a global so that classes that
// want to get info from the font menu can do so simply. ZTextWindow uses this, for example.
if ( mH && ( iType == appendFontNames ))
{
gFontMenuID = menuID;
SetMenuDimming( menuID, neverDim + dimTitle );
}
}
/*----------------------------------*** FINDMCMD ***---------------------------------*/
/*
find the command entry associated with the mSelection value.
---------------------------------------------------------------------------------------*/
void ZMenuBar::FindMCmd( const long mSelect, MenuCmd* aCmd )
{
long m;
MenuCmd mCmd;
for( m = 1; m <= theMenuCmds->CountItems(); m++ )
{
theMenuCmds->GetArrayItem( &mCmd, m );
// is this the one we want?
if ( HiWord( mSelect ) == mCmd.menuID &&
LoWord( mSelect ) == mCmd.itemID )
{
*aCmd = mCmd;
break;
}
}
}
/*--------------------------------*** FINDCOMMAND ***--------------------------------*/
/*
inverse operation- given a command, return the original menu item and ID.
---------------------------------------------------------------------------------------*/
void ZMenuBar::FindCommand( const long cmd, short* menuID, short* itemID )
{
long m;
MenuCmd mCmd;
for( m = 1; m <= theMenuCmds->CountItems(); m++ )
{
theMenuCmds->GetArrayItem( &mCmd, m );
// is this the one we want?
if ( cmd == mCmd.theCmd )
{
*menuID = mCmd.menuID;
*itemID = mCmd.itemID;
break;
}
}
}
/*------------------------------*** SETTITLEHILITE ***-------------------------------*/
/*
set the menu title hilite on or off
---------------------------------------------------------------------------------------*/
void ZMenuBar::SetTitleHilite( const short menuID, const Boolean state )
{
if ( state )
HiliteMenu( menuID );
else
HiliteMenu( 0 );
}
/*--------------------------------*** FINDMENUID ***---------------------------------*/
/*
returns the handle of the menu with the given ID, even if it has not been inserted into
the system list. This can only find menus that belong to this bar object however.
---------------------------------------------------------------------------------------*/
MenuHandle ZMenuBar::FindMenuID( const short menuID )
{
// returns the handle of the menu with the given ID. This works whether or not the menu
// was actually installed in the system menu list. If so, we use the toolbox call. If not
// we use the slightly slower method of looking through our private array of commands to
// locate the menu.
MenuInfRec mRec;
mRec.macMenu = GetMenuHandle( menuID );
if ( mRec.macMenu == NULL )
FindMenuInfo( menuID, &mRec );
return mRec.macMenu;
}
/*-------------------------------*** FINDMENUINFO ***--------------------------------*/
/*
returns the info record for the given menu ID. This info is used to determine the right
dimming and disposal behaviour for the menu.
---------------------------------------------------------------------------------------*/
void ZMenuBar::FindMenuInfo( const short menuID, MenuInfRec* mRec )
{
short n, i;
MenuInfRec mr;
n = theMenus->CountItems();
for( i = 1; i<= n; i++ )
{
theMenus->GetArrayItem( &mr, i );
if ( mr.menuID == menuID )
{
*mRec = mr;
break;
}
}
}
void ZMenuBar::GetMenuTitleRect( short menuID, Rect* tRect )
{
// return the title rect (in GLOBAL coordinates) of the menu in the main bar with the
// menu ID <menuID>. This returns the empty rect if no such menu was found or if the
// menu is not in the main bar.
Rect tr = {0,0,0,0};
MenuHandle mH;
mListHdl mListH;
short i, iMax, tLength;
GrafPtr savePort, wPort;
mListH = (mListHdl) LMGetMenuList();
if ( mListH )
{
FailNILParam( mH = GetMenuHandle( menuID ));
// if this handle is in the menu list, then this must be in the main bar.
// ASSUMPTION: hierarchical menus etc. are stored elsewhere.
iMax = (((*mListH)->lastMOffset ) / sizeof( mListEntry )) - 2;
for ( i = 0; i < iMax; i++ )
{
if ( mH == (*mListH)->mListItem[i].theMenu )
{
// found it! Now set up the rect
tr.top = 1;
tr.bottom = GetMBarHeight();
tr.left = (*mListH)->mListItem[i].leftEdge;
// the right edge is tricky- we need to get the title and figure the length
GetPort( &savePort );
GetWMgrPort( &wPort );
SetPort( wPort );
tLength = StringWidth((*mH)->menuData ) + 10;
SetPort( savePort );
tr.right = tr.left + tLength;
break;
}
}
}
*tRect = tr;
}
void ZMenuBar::SetZoomSourceToCommand( const long aCmd )
{
short menuID = 0, itemID = 0;
FindCommand( aCmd, &menuID, &itemID );
if ( menuID )
{
Rect r;
GetMenuTitleRect( menuID, &r );
InsetRect( &r, 20, 4 );
SetGlobalZoomSource( &r );
}
}